package com.nh.glory.admin.config;

import com.nh.glory.data.mapper.RoleMapper;
import com.nh.glory.data.mapper.UserMapper;
import com.nh.glory.ware.security.authentication.StatelessAuthenticationEntryPoint;
import com.nh.glory.ware.security.authentication.jwt.JwtAuthenticationProcessingFilter;
import com.nh.glory.ware.security.authentication.jwt.JwtAuthenticationProvider;
import com.nh.glory.ware.security.authentication.jwt.JwtFactory;
import com.nh.glory.ware.security.authentication.rest.AjaxAuthenticationProcessingFilter;
import com.nh.glory.ware.security.authentication.rest.AjaxAuthenticationProvider;
import com.nh.glory.ware.security.authentication.rest.AjaxAuthenticationSuccessHandler;
import com.nh.glory.ware.security.config.CustomCorsFilter;
import com.nh.glory.ware.security.config.SkipPathRequestMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;

import java.util.Arrays;
import java.util.List;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String ADMIN_ROOT_URL = "/**";

    private final StatelessAuthenticationEntryPoint authenticationEntryPoint;
    private final AjaxAuthenticationSuccessHandler successHandler;
    private final AuthenticationFailureHandler failureHandler;
    private final JwtFactory jwtFactory;
    private final UserMapper userMapper;
    private final RoleMapper roleMapper;
    private AuthenticationManager authenticationManager;

    @Value("${security.url.permit}")
    private String[] permitUrls;

    @Value("${security.url.login}")
    private String loginUrl;

    @Autowired
    public WebSecurityConfig(StatelessAuthenticationEntryPoint authenticationEntryPoint,
                             AjaxAuthenticationSuccessHandler successHandler,
                             AuthenticationFailureHandler failureHandler,
                             JwtFactory jsonWebTokenFactory,
                             UserMapper userMapper,
                             RoleMapper roleMapper) {
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.successHandler = successHandler;
        this.failureHandler = failureHandler;
        this.jwtFactory = jsonWebTokenFactory;
        this.userMapper = userMapper;
        this.roleMapper = roleMapper;
    }


    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(new AjaxAuthenticationProvider(userMapper, roleMapper, new BCryptPasswordEncoder()));
        auth.authenticationProvider(new JwtAuthenticationProvider(userMapper, roleMapper, jwtFactory));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable() // We don't need CSRF for JWT based authentication
                .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)

                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and()
                .authorizeRequests()
                .antMatchers(permitUrls)
                .permitAll()

                .and()
                .authorizeRequests()
                .antMatchers(ADMIN_ROOT_URL).authenticated() // Protected API End-points

                .and().logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())

                .and()
                .addFilterBefore(new CustomCorsFilter(ADMIN_ROOT_URL), LogoutFilter.class)
                .addFilterBefore(buildUsernamePasswordProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(buildJwtAuthenticationProcessingFilter(Arrays.asList(permitUrls)), UsernamePasswordAuthenticationFilter.class);
    }


    private AjaxAuthenticationProcessingFilter buildUsernamePasswordProcessingFilter() {
        AjaxAuthenticationProcessingFilter filter = new AjaxAuthenticationProcessingFilter(loginUrl, successHandler, failureHandler);
        filter.setAuthenticationManager(authenticationManager);
        return filter;
    }

    private JwtAuthenticationProcessingFilter buildJwtAuthenticationProcessingFilter(List<String> pathsToSkip) {
        SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, ADMIN_ROOT_URL);
        JwtAuthenticationProcessingFilter filter = new JwtAuthenticationProcessingFilter(matcher, jwtFactory, failureHandler);
        filter.setAuthenticationManager(authenticationManager);
        return filter;
    }

    @Autowired
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

}
